﻿#if UNITY_2019_4_OR_NEWER
#define UNITY_3D
using UnityEngine;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif

using System.Text;
using System.Collections.Generic;
using System.Reflection;
using System.Collections;

namespace Devil
{

    public delegate bool FilterDelegate<T>(T target);

    public delegate T PassDelegate<T>(T target);

    public delegate T ValueDelegate<T>();

    public delegate V GetterDelegate<in T, out V>(T target);

    public delegate void SetterDelegate<T, V>(T target, V value);

    public delegate int IdentifierDelegate<T>(T target);

    public delegate bool EquatableDelegate<T>(T a, T b);

    public static partial class ParallelUtils
    {
        public const int UPPER_2_LOWER = 'a' - 'A';
        static long _idPtr;
        static readonly object _lock = new object();
        //static ObjectPool<StringBuilder> mBuilders = new ObjectPool<StringBuilder>(64, () => new StringBuilder(128));

        public static bool IsMainThread => ParallelDispatcher.IsMainThread;

        public static StringBuilder GetBuilder()
        {
            var builder = CachedObject<StringBuilder>.GetObject();
            builder.Clear();
            return builder;
        }

        public static StringBuilder GetBuilder(string data)
        {
            StringBuilder builder = CachedObject<StringBuilder>.GetObject();
            builder.Clear();
            if (!string.IsNullOrEmpty(data))
                builder.Append(data);
            return builder;
        }

        public static string ReturnRelease(StringBuilder builder)
        {
            string ret = builder.ToString();
            CachedObject<StringBuilder>.Release(builder);
            return ret;
        }

        public static void Release(StringBuilder builder)
        {
            CachedObject<StringBuilder>.Release(builder);
        }

        public static StringBuilder AppendTab(this StringBuilder buffer, int tabs)
        {
            for (int i = 0; i < tabs; i++)
                buffer.Append('\t');
            return buffer;
        }

        public static StringBuilder AppendLine(this StringBuilder buffer, string content)
        {
            if (buffer.Length > 0)
                NextLine(buffer);
            buffer.Append(content);
            return buffer;
        }

        public static StringBuilder AppendWithLinePrefix(this StringBuilder buffer, string linePrefix, string content)
        {
            var index = buffer.Length;
            buffer.Append(content);
            if (!string.IsNullOrEmpty(linePrefix))
            {
                if (index > 0)
                    index--;
                for (int i = buffer.Length - 1; i >= index; i--)
                {
                    if (buffer[i] == '\n' && i < buffer.Length - 1)
                    {
                        buffer.Insert(i + 1, linePrefix);
                    }
                }
            }
            return buffer;
        }

        public static StringBuilder NextLine(this StringBuilder buffer)
        {
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
            buffer.Append("\r\n");
#else
            buffer.Append('\n');
#endif
            return buffer;
        }

        public static string GetDelegateName(this System.Delegate dele)
        {
            var calls = dele == null ? null : dele.GetInvocationList();
            var size = calls == null ? 0 : calls.Length;
            if (size == 0)
                return "'null delegate'";
            var buf = GetBuilder();
            for (int i = 0; i < size; i++)
            {
                if (i > 0)
                    buf.Append("; ");
                buf.Append(dele.Method.DeclaringType.Name);
                buf.Append('.');
                buf.Append(dele.Method.Name);
            }
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b);
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b, object c)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b).Append(c);
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b, object c, object d)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b).Append(c).Append(d);
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b, object c, object d, object e)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b).Append(c).Append(d).Append(e);
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b, object c, object d, object e, object f)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f);
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b, object c, object d, object e, object f, object g)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g);
            return ReturnRelease(buf);
        }

        public static string Concat(object a, object b, object c, object d, object e, object f, object g, params object[] others)
        {
            var buf = GetBuilder();
            buf.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g);
            for (int i = 0; i < others.Length; i++)
            {
                buf.Append(others[i]);
            }
            return ReturnRelease(buf);
        }

        // 驼峰命名
        public static string ToCamelName(string title, bool insertSpace)
        {
            var toUpper = 'A' - 'a';
            byte type_lower = 1, type_upper = 2, type_other = 3;
            var buf = GetBuilder(title);
            for (int i = buf.Length - 1; i >= 0; i--)
            {
                var c = buf[i];
                byte ctype;
                if (c >= 'A' && c <= 'Z')
                    ctype = type_upper;
                else if (c >= 'a' && c <= 'z')
                    ctype = type_lower;
                else
                    ctype = type_other;
                var pre = i > 0 ? buf[i - 1] : '\0';
                if ((pre == '\0' || pre == ' ' || pre == '\t') && ctype == type_lower)
                {
                    ctype = type_upper;
                    c = (char)(c + toUpper);
                }
                if (insertSpace)
                {
                    byte ptype;
                    if (pre >= 'A' && pre <= 'Z')
                        ptype = type_upper;
                    else if (pre >= 'a' && pre <= 'z')
                        ptype = type_lower;
                    else
                        ptype = type_other;
                    if (ctype == type_upper && ptype == type_lower && i > 0)
                        buf.Insert(i, ' ');
                }
            }
            return ReturnRelease(buf);
        }

        public static string ToMask(int value, string connector, string[] names)
        {
            var buf = GetBuilder();
            //int n = 1;
            for (int i = 0; i < Mathf.Min(32, names.Length); i++)
            {
                if ((value & (1 << i)) != 0)
                {
                    if (buf.Length > 0)
                        buf.Append(connector);
                    buf.Append(names[i]);
                }
            }
            return ReturnRelease(buf);
        }

        public static string ToMask<T>(int value, string connector, bool asIndex = false) where T : System.Enum
        {
            var buf = GetBuilder();
            var names = System.Enum.GetNames(typeof(T));
            var values = System.Enum.GetValues(typeof(T));
            for (int i = 0; i < names.Length; i++)
            {
                var v = (int)values.GetValue(i);
                if (asIndex)
                    v = 1 << v;
                if ((value & v) != 0)
                {
                    if (buf.Length > 0)
                        buf.Append(connector);
                    buf.Append(names[i]);
                }
            }
            return ReturnRelease(buf);
        }

        public static string Gather<T>(IList<T> lst, int count = -1, string seperator = ",")
        {
            if (lst == null)
                return "";
            if (count < 0)
                count = lst.Count;
            var buf = GetBuilder();
            for (int i = 0; i < count; i++)
            {
                if (i > 0)
                    buf.Append(seperator);
                buf.Append(lst[i]);
            }
            return ReturnRelease(buf);
        }

        public static string Gather(IEnumerator iter, string seperator = ",")
        {
            if (iter == null)
                return "";
            StringBuilder buf = GetBuilder();
            if (string.IsNullOrEmpty(seperator))
            {
                while (iter.MoveNext())
                {
                    buf.Append(iter.Current);
                }
            }
            else
            {
                bool first = true;
                while (iter.MoveNext())
                {
                    if (first)
                        first = false;
                    else
                        buf.Append(seperator);
                    buf.Append(iter.Current);
                }
            }
            return ReturnRelease(buf);
        }


        public static int Max(int a, int b)
        {
            return a < b ? b : a;
        }

        public static int Min(int a, int b)
        {
            return a < b ? a : b;
        }

        public static void EnsureNativeSize<T>(ref T[] arr, int dataCount, int newSize, float resizeRatio = 1.5f)
        {
            if (newSize > 0 && arr == null || arr.Length < newSize)
            {
                var size = Max(arr == null ? (newSize + 4) : (int)System.Math.Ceiling(arr.Length * resizeRatio), newSize);
                var cp = new T[size];
                if (dataCount > 0)
                    System.Array.Copy(arr, cp, dataCount);
                arr = cp;
            }
        }
        public static int GenUniqueID()
        {
            var v = System.DateTime.UtcNow.Ticks;
            lock (_lock)
            {
                return unchecked((int)((v << 5) - v + ++_idPtr));
            }
        }

        /// <summary>
        /// 比较版本定义大小
        /// </summary>
        /// <param name="version1"></param>
        /// <param name="version2"></param>
        /// <returns></returns>
        public static int CompareVersionDefine(string version1, string version2)
        {
            if (string.IsNullOrEmpty(version1))
                return string.IsNullOrEmpty(version2) ? 0 : -1;
            else if (string.IsNullOrEmpty(version2))
                return 1;
            var i1 = version1.IndexOf('.');
            var i2 = version2.IndexOf('.');
            string ver1, ver2;
            ver1 = i1 > 0 ? version1.Substring(0, i1) : version1;
            ver2 = i2 > 0 ? version2.Substring(0, i2) : version2;
            int n1, n2, v;
            if (int.TryParse(ver1, out n1) && int.TryParse(ver2, out n2))
                v = n1 < n2 ? -1 : (n1 > n2 ? 1 : 0);
            else
                v = ver1.CompareTo(ver2);
            while (v == 0 && i1 > 0 && i2 > 0)
            {
                var i1end = version1.IndexOf('.', i1 + 1);
                var i2end = version2.IndexOf('.', i2 + 1);
                ver1 = version1.Substring(i1 + 1, (i1end > 0 ? i1end : version1.Length) - i1 - 1);
                ver2 = version2.Substring(i2 + 1, (i2end > 0 ? i2end : version2.Length) - i2 - 1);
                if (int.TryParse(ver1, out n1) && int.TryParse(ver2, out n2))
                    v = n1 < n2 ? -1 : (n1 > n2 ? 1 : 0);
                else
                    v = ver1.CompareTo(ver2);
                i1 = i1end;
                i2 = i2end;
            }
            if (v != 0)
                return v;
            else if (i1 > 0)
                return 1;
            else if (i2 > 0)
                return -1;
            else
                return v;
        }

        public static char ToLower(char c)
        {
            if (c <= 'Z' && c >= 'A')
                return (char)(c + UPPER_2_LOWER);
            else
                return c;
        }

        public static char ToUpper(char c)
        {
            if (c <= 'z' && c >= 'a')
                return (char)(c - UPPER_2_LOWER);
            else
                return c;
        }

        public static char GetCharIgnoreCase(char c)
        {
            if (c >= 'A' && c <= 'Z')
                return (char)(c + UPPER_2_LOWER);
            else
                return c;
        }

        public static char GetCharIgnoreCase(this string str, int index)
        {
            char c = str[index];
            if (c >= 'A' && c <= 'Z')
                return (char)(c + UPPER_2_LOWER);
            else
                return c;
        }

        public static bool StartsWithIgnoreCase(this string str, string pattern)
        {
            int len = pattern.Length;
            if (str.Length < len)
                return false;
            for (int i = 0; i < len; i++)
            {
                if (GetCharIgnoreCase(str, i) != GetCharIgnoreCase(pattern, i))
                    return false;
            }
            return true;
        }
        public static bool EndsWithIgnoreCase(this string str, string pattern)
        {
            int len = pattern.Length;
            int off = str.Length - len;
            if (off < 0)
                return false;
            for (int i = len - 1; i >= 0; i--)
            {
                if (GetCharIgnoreCase(str, off + i) != GetCharIgnoreCase(pattern, i))
                    return false;
            }
            return true;
        }

        // 比较字符串开始相同部分长度（忽略大小写）
        public static int SameStartsLengthWithIgnoreCase(this string str1, string str2)
        {
            var len1 = str1 == null ? 0 : str1.Length;
            var len2 = str2 == null ? 0 : str2.Length;
            var len = Mathf.Min(len1, len2);
            for (int i = 0; i < len; i++)
            {
                if (GetCharIgnoreCase(str1[i]) != GetCharIgnoreCase(str2[i]))
                    return i;
            }
            return len;
        }
        // 比较字符串末尾相同部分长度（忽略大小写）
        public static int SameEndsLengthWithIgnoreCase(this string str1, string str2)
        {
            var len1 = str1 == null ? 0 : str1.Length;
            var len2 = str2 == null ? 0 : str2.Length;
            var len = Mathf.Min(len1, len2);
            for (int i = 0; i < len; i++)
            {
                if (GetCharIgnoreCase(str1[len1 - i - 1]) != GetCharIgnoreCase(str2[len2 - i - 1]))
                    return i;
            }
            return len;
        }

        public static bool EqualsIgnoreCase(this string a, string b)
        {
            int la = a == null ? 0 : a.Length;
            int lb = b == null ? 0 : b.Length;
            if (la != lb)
                return false;
            return la == 0 || a.Equals(b, System.StringComparison.OrdinalIgnoreCase);
        }

        public static bool EqualsIgnoreCase(this string a, StringBuilder builder)
        {
            var la = a == null ? 0 : a.Length;
            var lb = builder == null ? 0 : builder.Length;
            if (la != lb)
                return false;
            for (int i = 0; i < la; i++)
            {
                if (ToLower(a[i]) != ToLower(builder[i]))
                    return false;
            }
            return true;
        }

        public static int ToHash(this string str)
        {
            int hash = 0;
            int len = str == null ? 0 : str.Length;
            for (int i = 0; i < len; i++)
            {
                hash = MathExt.Hash(hash, str[i]);
            }
            return hash;
        }


        public static int IgnoreCaseToHash(this string str)
        {
            int hash = 0;
            int len = str == null ? 0 : str.Length;
            for (int i = 0; i < len; i++)
            {
                hash = MathExt.Hash(hash, GetCharIgnoreCase(str, i));
            }
            return hash;
        }

        public static int IgnoreCaseToHash(this char[] sequence, int offset, int len)
        {
            int hash = 0;
            int end = offset + len;
            for (int i = offset; i < end; i++)
            {
                //hash = (hash << 5) - hash + ToLower(sequence[i]);
                hash = MathExt.Hash(hash, ParallelUtils.ToLower(sequence[i]));
            }
            return hash;
        }

        public static bool ContainsIgnoreCase(this string text, string sub)
        {
            var len = text.Length;
            var size = sub.Length;
            var first = ParallelUtils.ToLower(sub[0]);
            int matchLen = 0;
            for (int i = 0; i < len && matchLen < size; i++)
            {
                var c0 = ParallelUtils.ToLower(text[i]);
                var c1 = ParallelUtils.ToLower(sub[matchLen]);
                if (c0 == c1)
                    matchLen++;
                else if (c0 == first)
                    matchLen = 1;
                else
                    matchLen = 0;
            }
            return matchLen == size;
        }

        public static void GetTypesInAssembly(Assembly assembly, ICollection<System.Type> collection, FilterDelegate<System.Type> filter)
        {
            if (assembly == null)
                return;
            try
            {
                var types = assembly.GetTypes();
                for (int i = 0; i < types.Length; i++)
                {
                    if (filter == null || filter(types[i]))
                        collection.Add(types[i]);
                }
            }
            catch (System.Exception e)
            {
                Debug.LogErrorFormat("Faild to load assembly: {0}\n{1}", assembly, e);
            }
        }

        public static void GetAssemblyTypes(ICollection<System.Type> types, FilterDelegate<System.Type> filter = null, FilterDelegate<Assembly> assemblyFilter = null)
        {
            Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                if (assemblyFilter == null || assemblyFilter(assembly))
                {
                    GetTypesInAssembly(assembly, types, filter);
                }
                //foreach (Type foundType in GetTypesInAssembly(assembly, predicate))
                //    foundTypes.Add(foundType);
            }
            //return foundTypes;
        }

#if UNITY_EDITOR
        public static void DisplayProgressBar(this ParallelDispatcher.Handler handler, bool cancelable, string title = null)
        {
            Schedule(new ProgressBar(handler, title, cancelable), true);
        }

        class ProgressBar : IYieldCommand, IIndependentCommandSignal
        {
            string mTitle;
            ParallelDispatcher.Handler mHandler;
            bool mCancelable;

            internal ProgressBar(ParallelDispatcher.Handler handler, string title, bool cancelable)
            {
                mHandler = handler;
                mCancelable = cancelable;
                if(string.IsNullOrEmpty(title))
                {
                    var cmd = handler.GetCommand();
                    mTitle = cmd == null ? "Invalid Cmd" : $"Cmd: {cmd.GetType().Name}";
                }
                else
                {
                    mTitle = title;
                }
            }

            public bool IsBreakable(bool isInterruptByDependent)
            {
                return false;
            }

            IEnumerator IYieldCommand.Execute()
            {
                var cmd = mHandler.GetCommand();
                var desc = mHandler.ToString();
                if (mCancelable)
                {
                    var cancel = EditorUtility.DisplayCancelableProgressBar(mTitle, desc, 0f);
                    if (cancel)
                        mHandler.Abort();
                    while (mHandler.IsAlive)
                    {
                        var exec = mHandler.GetExecution();
                        cancel = EditorUtility.DisplayCancelableProgressBar(mTitle, desc, exec.progress);
                        if (cancel)
                            mHandler.Abort();
                        yield return null;
                    }
                }
                else
                {
                    EditorUtility.DisplayProgressBar(mTitle, desc, 0f);
                    while (mHandler.IsAlive)
                    {
                        var exec = mHandler.GetExecution();
                        EditorUtility.DisplayProgressBar(mTitle, desc, exec.progress);
                        yield return null;
                    }
                }
                EditorUtility.ClearProgressBar();
            }
        }

#endif
    }

}
